import java.awt.Graphics;
import java.awt.Rectangle;
import java.awt.image.ImageObserver;
import java.util.ArrayList;


public class Defender extends GameObject{

	static final int starty = InvaderSaver.getGameHeight() - 30;		// always at this height
	static final int startx = 24;	// always spawns in the same place
	static final int MAXAMMO = 1;
	static final int PROJECTILE_SPEED = -16/InvaderSaver.GAMESPEED_MODIFIER;
	static final int FIRE_COOLDOWN = 5*InvaderSaver.GAMESPEED_MODIFIER;
	static final int MOVE_SPEED = 8/InvaderSaver.GAMESPEED_MODIFIER;
	static final int DEATH_ANIM_COOLDOWN = 1*InvaderSaver.GAMESPEED_MODIFIER;
	static int fireWait;
	static int liveAmmo;
	static int deathAnimWait;
	
	static Wave waveAIAccess;
	static ArrayList<Base> baseAIAccess;
	
	private Boolean dead = false;
	
	// AI control variables
	private Invader target = null;
	
//	InvaderSaver.ControlMode control;
	InvaderSaver.TargetingAI targetingMode;
	InvaderSaver.AimingAI aimingMode;
	InvaderSaver.EvadingAI evadingMode;
	
	// no Controller specified - default to BASIC_AI
	public Defender(){
		super(startx, starty, SpriteLib.DEFENDER);
//		this.control = InvaderSaver.ControlMode.CLOSEST;
		this.targetingMode = InvaderSaver.TargetingAI.RANDOM;
		this.aimingMode = InvaderSaver.AimingAI.UNDER;
		this.evadingMode = InvaderSaver.EvadingAI.SIMPLE;
		liveAmmo = 0;
		fireWait = 0;
		deathAnimWait = 0;
		
		waveAIAccess = null;
		baseAIAccess = null;
	}
	
	// allows choice of AI scripts or human player.
	public Defender(InvaderSaver.TargetingAI tAI, InvaderSaver.AimingAI aAI, InvaderSaver.EvadingAI eAI){
		super(startx, starty, SpriteLib.DEFENDER);
//		this.control = control;
		this.targetingMode = tAI;
		this.aimingMode = aAI;
		this.evadingMode = eAI;
		liveAmmo = 0;
		fireWait = 0;
		deathAnimWait = 0;

		waveAIAccess = null;
		baseAIAccess = null;
	}
	
	/**
	 * Updates the state of the Defender, using either user input or an AI script.
	 * The two variables are only used if an AI script is in use.
	 */
	public void update(Wave wave, ArrayList<Base> bases){
		if(dead){
			if(deathAnimWait > DEATH_ANIM_COOLDOWN){
				image_index++;
				deathAnimWait = 0;
			}
			deathAnimWait++;
			return;
		}
		
		if(fireWait > 0){
			fireWait--;
		}

		// Enable access to the current state of the game
		waveAIAccess = wave;
		baseAIAccess = bases;
		// the projectile list can be accessed via ProjectileControl.getProjectiles()
		
		/*
		 * There are three main areas of control: Target Selection, Aiming, and Projectile Evasion
		 * Each can be approached in different ways, so I've got a switch statement for each area, so 
		 * different strategies (AI scripts) can be run in each case, and combined in different ways.
		 */
		
		switch(targetingMode){
		case RANDOM:
			randomTargetAI();
			break;
		case LEFTMOST:
			leftmostTargetAI();
			break;
		case RIGHTMOST:
			rightmostTargetAI();
			break;
		case LOWEST:
			lowestTargetAI();
			break;
		case SQUARE:
			squareTargetAI();
			break;
		default:
			// human control; do nothing	
		}
		
		switch(aimingMode){
		case UNDER:
			underAimAI();
			break;
		case ANTICIPATE:
			anticipateAimAI();
			break;
		default:
				// human control, do nothing
		}
		
		switch(evadingMode){
		case SIMPLE:
			simpleEvadeAI();
			break;
		case FORESIGHT:
			foresightEvadeAI();
			break;
		default:
				// human control, do nothing
		}
		
		// after AI scripts have modified the controls as wanted, implement actions.
		executeActions();
	}
	
	/* *************************************************************************************
	 * 					TARGETING AI SCHEMES
	 * *************************************************************************************/

	/**
	 * Choose a random Invader to target, if one is not already selected
	 */
	private void randomTargetAI() {
		
		// choose a valid new target if current one is dead
		if((target == null || !target.isAlive())){
			ArrayList<Invader> invaders = waveAIAccess.getInvaders(); // get the list of potentials 
			
			if(invaders.size() > 0){ // make sure a target exists
				int newtarg = (int)(Math.random()*invaders.size());
				target = invaders.get(newtarg);
			}
		}
	}
	
	/**
	 * Picks whichever Invader is furthest to the left side of the screen to kill
	 */
	private void leftmostTargetAI(){
		if((target == null || !target.isAlive())){
			ArrayList<Invader> invaders = waveAIAccess.getInvaders();
			
			if(invaders.size() > 0){
				target = invaders.get(0); // tentatively choose the first one
				
				for(Invader i: invaders){
					if(i.x_position <= target.x_position){
						target = i;
					}
				}	
			}
		}
	}
	
	/**
	 * Picks whichever Invader is furthest to the right  side of the screen to kill
	 */
	private void rightmostTargetAI(){
		if((target == null || !target.isAlive())){
			ArrayList<Invader> invaders = waveAIAccess.getInvaders();
			
			if(invaders.size() > 0){
				target = invaders.get(0); // tentatively choose the first one
				
				for(Invader i: invaders){
					if(i.x_position >= target.x_position){
						target = i;
					}
				}	
			}
		}
	}
	
	/**
	 * Picks whichever Invader is lowest to kill
	 */
	private void lowestTargetAI(){
		if((target == null || !target.isAlive())){
			ArrayList<Invader> invaders = waveAIAccess.getInvaders();
			
			if(invaders.size() > 0){
				target = invaders.get(0); // tentatively choose the first one
				
				for(Invader i: invaders){
					if(i.y_position > target.y_position){
						target = i;
					}
					else if(i.y_position == target.y_position){
						// target whichever is closer to the defender
						if(Math.abs(x_position - i.x_position) < Math.abs(x_position - target.x_position)){
							target = i;
						}
					}
				}	
			}
		}
	}
	
	/**
	 * Attempts to keep the enemy formation as square as possible by shaving edges 
	 */
	private void squareTargetAI(){
		InvaderSaver.UP = false;
		if(target == null || !target.isAlive()){
			int maxx = 0, maxy = 0, minx = InvaderSaver.getGameWidth(), miny = InvaderSaver.getGameHeight();
			
			for(Invader i: waveAIAccess.getInvaders()){
				if(i.x_position < minx) { minx = i.x_position; }
				if(i.x_position > maxx) { maxx = i.x_position; }
				if(i.y_position < miny) { miny = i.y_position; }
				if(i.y_position > maxy) { maxy = i.y_position; }
			}
			
			int waveWidth = maxx - minx;
			int waveHeight = maxy - miny;
			
			if(waveHeight > waveWidth){
				lowestTargetAI();
			}
			else {
				if(Math.abs(minx - x_position) < Math.abs(maxx - x_position)){
					leftmostTargetAI();
				}
				else{
					rightmostTargetAI();
				}
			}
		}
	}
	
	/* *************************************************************************************
	 * 					AIMING AI SCHEMES
	 * *************************************************************************************/
	
	/**
	 * Attempt to get under the target. Fire.
	 */
	private void underAimAI(){
		InvaderSaver.UP = false; // hold fire until in position
		// If we have a target, go kill it
		if(target != null){
			int positionalDifference = x_position - target.x_position;
			
			// chase right
			if(positionalDifference < -target.sprite.image_width/3){
				InvaderSaver.LEFT = false;
				InvaderSaver.RIGHT = true;
			}
			// chase left
			if(positionalDifference > target.sprite.image_width/3){
				InvaderSaver.RIGHT = false;
				InvaderSaver.LEFT = true;
			}
			// shooting
			if(Math.abs(x_position - target.x_position) <= target.sprite.image_width/3){
				InvaderSaver.RIGHT = false;
				InvaderSaver.LEFT = false;   // Yeah, I don't have to tell him to stay still, but then he looks weird
				InvaderSaver.UP = true;  // FIRE
			}
		}
	}
	
	/**
	 * attempts to calculate where the target is going to be in the future, and ensure a projectile is there to meet it.
	 * Currently does not account for the game boundaries / Invader direction changes // TODO?
	 */
	private void anticipateAimAI(){
		InvaderSaver.UP = false;
		if(target != null && target.isAlive() == true){
			// find the vertical distance, and thereby then number of steps it will take a bullet to reach that high
			int yPositionDifference = (int)Math.abs(y_position - target.y_position);// - SpriteLib.INVADER_LOW.image_height/2 - SpriteLib.DEFENDER_LASER.image_height / 2;
			say("----------------------");
			say("vertical difference: " + yPositionDifference);
			say("horizontal difference: " + Math.abs(x_position - target.x_position));
			int stepsToImpact = (int)Math.ceil(yPositionDifference / Math.abs(PROJECTILE_SPEED));
			say("StepsToImpact: " + stepsToImpact);
			
			// figure out how far the invader will travel in that time
			say("wait till next: " + Wave.getTimeUntilNextMove());
			say("movementInterval: " + Wave.getMovementInterval());
			say("invaderMoveSpeed: " + Wave.getInvaderMoveSpeed());
			int moves = 0;
			if(stepsToImpact >= Wave.getTimeUntilNextMove()){
				stepsToImpact -= Wave.getTimeUntilNextMove();
				moves = 1;	
			}
			while(stepsToImpact >= Wave.getMovementInterval()){
				moves++;
				stepsToImpact -= Wave.getMovementInterval();
			}
			say("Moves before impact: " + moves);
			int invaderMoveDistance =moves * Wave.getInvaderMoveSpeed();
			say("InvaderMoveDistance: " + invaderMoveDistance);
			
			// go there, kill it
			int positionalDifference = x_position - (target.x_position + invaderMoveDistance);
			
			say("positionalDifference: " + positionalDifference);
			
			// if the invader is too fast to catch it before it gets to the edge, wait for it in the middle somewhere
			boolean tooSlow = MOVE_SPEED <= Math.abs(Wave.getInvaderMoveSpeed()/Wave.getMovementInterval());
			boolean leftBehind = (Wave.direction < 0) == (target.x_position+invaderMoveDistance - x_position < 0);
			if(tooSlow && leftBehind){
				int waitPosition = 0;
				if(Wave.getInvaderMoveSpeed() > 0){ // invaders are moving right
					waitPosition = (int)(InvaderSaver.getGameWidth() * (2.0/3.0));
				}
				else { // invaders are moving left
					waitPosition = (int)(InvaderSaver.getGameWidth() * (1.0/3.0));
				}
				
				if(x_position > (waitPosition + MOVE_SPEED)){
					InvaderSaver.LEFT = true;
					InvaderSaver.RIGHT = false;
				}
				else if(x_position < (waitPosition - MOVE_SPEED)){
					InvaderSaver.RIGHT = true;
					InvaderSaver.LEFT = false;
				}
			}
			else { // pursue normally
				// everything else is  just copied from underAimAI()
				
				// chase right
				if(positionalDifference < -target.sprite.image_width/3){
					InvaderSaver.LEFT = false;
					InvaderSaver.RIGHT = true;
				}
				// chase left
				if(positionalDifference > target.sprite.image_width/3){
					InvaderSaver.RIGHT = false;
					InvaderSaver.LEFT = true;
				}
			}
			
			// shooting
			if(Math.abs(positionalDifference) <= target.sprite.image_width/3){
				// we are in position to hit the target... however, we want to check to make sure we aren't destroying our own defenses first.
				// make a Rectangle with the width and x_position of a laser, but extending vertically through the whole gamespace
				Rectangle lRect = new Rectangle(x_position - SpriteLib.DEFENDER_LASER.image_width / 2, y_position, SpriteLib.DEFENDER_LASER.image_width, SpriteLib.DEFENDER_LASER.image_height);
				lRect.y = 0;
				lRect.height = InvaderSaver.getGameHeight();
				
				boolean belowBase = false;
				//////////////  now, see if a fired projectile would hit any bases
				for(Base b: baseAIAccess){
					if(lRect.intersects(b.getRect())){
						belowBase = true;
					}
				}
			
				if(!belowBase){
					InvaderSaver.RIGHT = false;
					InvaderSaver.LEFT = false;   // Yeah, I don't have to tell him to stay still, but then he looks weird
					InvaderSaver.UP = true;  // FIRE
					say("firing");
				}
			}
		}
	}
	
	private void opportunisticAimAI(){
		// if we can fire
		//// if an Invader would be hit if we did
		//// FIRE
	}

	/* *************************************************************************************
	 * 					EVASION AI SCHEMES
	 * *************************************************************************************/
	
	/**
	 * First attempt at a life-preserving instinct.
	 * Tries to avoid destruction by evaluating the current position relative to incoming lasers to determine how much
	 * 'danger' it is in compared to adjacent spaces, taking only horizontal distance from lasers into account.
	 * It was initially supposed to account for vertical distances, but seems not to in practice. TODO? 
	 */
	private void simpleEvadeAI(){
		
		// try not to die, override previous orders if necessary
		int acceptableRisk = 5; // seems arbitrary. Might be. Can't recall
		if(simpleBadnessMetric(x_position) > acceptableRisk){ // we are in danger, MOVE
			if(simpleBadnessMetric(x_position + MOVE_SPEED) > simpleBadnessMetric(x_position - MOVE_SPEED)){
				InvaderSaver.LEFT = true;
				InvaderSaver.RIGHT = false;
			}
			else{
				InvaderSaver.LEFT = false;
				InvaderSaver.RIGHT = true;
			}
		}
	}
	
	/**
	 * Uses a look-ahead to determine if the currently-chosen course of action will result in death within a given time window.
	 * If so, it finds an alternate (safe) course of action that is as close as possible to the original.
	 */
	private void foresightEvadeAI(){
		// first, make a copy of the list of Projectiles
		ArrayList<Projectile> PL = ProjectileControl.getProjectiles();
		ArrayList<Projectile> lasers = new ArrayList<Projectile>();
		for(Projectile p: PL){
			lasers.add(new Projectile(p.x_position, p.y_position + p.getVelocity(), p.getVelocity(), p.sprite, p.getOwner()));
		}
		
		// establish how far in the future we are concerned about
		int foresight = 5*InvaderSaver.GAMESPEED_MODIFIER; // the number of steps into the future we want to worry about
		// foresight is 5 here because it is the approximate number of steps necessary for the defender
		// to pass under an approaching laser at 30 fps.
		
		/* Now, remove any projectiles that are
		 * - Owned by the Defender
		 * - Already below the Defender
		 * - Going to hit a Base
		 * - Far enough above or to the side that it won't affect the Defender in the next 5 steps at 30fps
		 */
		for(int i = 0; i < lasers.size(); i++){
			// first, compile all necessary statistics and other info about the given lazer
			Projectile laz = lasers.get(i);
			
			////////////// info about juxtaposition with the Defender
			int contactHeight = this.y_position - this.height/2 - laz.height/2;
			// technically, contactHeight will be 6px lower due to the diminished Defender hitbox, but whatever
			
			int safeHeight = contactHeight - laz.getVelocity()*foresight;
			int safeHDistance = laz.width/2 + this.width/2 + Defender.MOVE_SPEED*foresight;
			Boolean tooFarAway = Math.abs(this.x_position - laz.x_position) > safeHDistance;
			Boolean belowDefender = ((this.y_position + this.height/2) - (laz.y_position - laz.height/2)) <= 0;// laz.getVelocity();
			////////////// info about base hittage
			Boolean aboveBase = false;
			// make a Rectangle with the width and x_position of laz, but extending vertically through the whole gamespace
			Rectangle lRect = new Rectangle(laz.getRect());
			lRect.y = 0;
			lRect.height = InvaderSaver.getGameHeight();
			
			//////////////  now, see if laz would hit any bases
			for(Base b: baseAIAccess){
				if(lRect.intersects(b.getRect())){
					aboveBase = true;
				}
			}
			
//			say("owner: " + laz.getOwner());
//			say("below: " + belowDefender);
//			say("abovebase: " + aboveBase);
//			say("too high: " + (safeHeight > laz.y_position));
//			say("far away: " + tooFarAway);
			
			//////////////// now take the previously-gathered stats and do the actual pruning
			if(laz.getOwner() == ProjectileControl.Owner.DEFENDER || belowDefender || aboveBase || safeHeight > laz.y_position || tooFarAway){
				lasers.remove(i);
				i--; // to account for removing one of the objects
				continue;
			}
		} // end of projectile-pruning for loop
		
		if(lasers.size() > 0){ // then we must ensure we are not marching to our doom
		
			/* Now we have a list of lazers that can affect the Defender directly within the next 5 steps
			 * Next we want to check if we are in danger at the next position, and if so, perform evasive actions.  
			 */
			// build a LocalSpace object
			LocalSpace currentSpace = new LocalSpace(lasers, this.getRect());
			
			// figure out the preferred order to try movement options
			int offsetsToTry[] = {0, MOVE_SPEED, MOVE_SPEED * -1};  // try stay, then right, then left
			if(InvaderSaver.RIGHT){ /// try right, then stay, then left.
				offsetsToTry[0] = MOVE_SPEED;
				offsetsToTry[1] = 0;
			}
			if(InvaderSaver.LEFT){  // try left, then stay, then right
				offsetsToTry[0] = -1 * MOVE_SPEED;
				offsetsToTry[1] = 0;
				offsetsToTry[2] = MOVE_SPEED;
			}
			
			// determine the appropriate move for the next step taking danger and our previously-chosen preference into account
			int newMove = currentSpace.evaluateMove(offsetsToTry, foresight);
			
			if(newMove < 0){
				InvaderSaver.LEFT = true;
				InvaderSaver.RIGHT = false;
			}
			else if(newMove > 0){
				InvaderSaver.LEFT = false;
				InvaderSaver.RIGHT = true;
			}
			else{
				InvaderSaver.LEFT = false;
				InvaderSaver.RIGHT = false;
			}
		}
	}
	
	/* *************************************************************************************
	 * 					AI HELPER METHODS/PARAPHERNALIA
	 * *************************************************************************************/
	
	/* provides a basic measure for the "badness" of the current position, based on
	 * the current vertical distance between the Defender and incoming lasers,
	 * with a strong multiplier based on projectile height. 
	 * - used by simpleEvadeAI()
	 */
	private int simpleBadnessMetric(int xPos){
		ArrayList<Projectile> lasers = ProjectileControl.getProjectiles();
		
		int badness = 0;
		
		for(Projectile p: lasers){
			int xDif = Math.abs(xPos - p.x_position);
			int yDif = y_position - p.y_position;
			// the farthest the Defender will have to move to escape a single Projectile, with slight buffer
			int maxBadness = sprite.image_width/2 + p.sprite.image_width/2 + MOVE_SPEED;
			int badnessMultiplier = 0;
			
			// if we will be hit in the next half-second
			if(yDif < p.sprite.image_height/2+sprite.image_height/2 + p.getVelocity()*InvaderSaver.STEPS_PER_SECOND){
				badnessMultiplier = 5;
			}// next second
			else if(yDif < p.sprite.image_height/2+sprite.image_height/2 + p.getVelocity()*InvaderSaver.STEPS_PER_SECOND/2){
				badnessMultiplier = 10;
			}
			
			if(xDif >= maxBadness){
				continue; // laser is far enough away to not pose a threat.
			}
			else{ // evaluate danger
//				int maxBadness = sprite.image_width/2;
				badness += (maxBadness - xDif)*badnessMultiplier;
			}
		}
		
		return badness;
	}
	
	/**
	 * Models the game, including the Defender object (represented as a Rectangle), and any projectile that is within a number of steps of contact with the Defender
	 * Does not model Invaders, Bases, or Bonuses
	 * @author Chris White
	 * - Used by foresightEvadeAI()
	 */
	class LocalSpace{
		
		ArrayList<Projectile> lazers;
		Rectangle defender;
		
		public LocalSpace( ArrayList<Projectile> l, Rectangle d){
			lazers = l;
			defender = d;
		}
		
		/*
		 * simulate advancing the game one step. Automatically "moves" lasers, and "moves"
		 * the defender according to dMove, which can be positive or negative. Returns the resulting localSpace
		 */
		public LocalSpace advanceStep(int dMove){
//			say("dMove = " + dMove);
//			say("position: " + defender.x);
			ArrayList<Projectile> newLazers = new ArrayList<Projectile>();
			for(Projectile p : lazers){ // copy the projectiles into the new localSpace, advancing their position as well
				newLazers.add(new Projectile(p.x_position, p.y_position + p.getVelocity(), p.getVelocity(), p.sprite, p.getOwner()));
			}
			// do the same for the defender TODO: account for screen edges (cannot move offscreen) Or, allow offscreen moves in-game?
			Rectangle newDefender = new Rectangle(defender.x, defender.y, defender.width, defender.height);
			if(dMove > 0 && newDefender.x+dMove < InvaderSaver.getGameWidth()-newDefender.width/2){
				newDefender.x += dMove;
//				say("Moving right");
			}
			else if(dMove < 0 && newDefender.x+dMove > newDefender.width/2){
				newDefender.x += dMove;
//				say("Moving left");
			}
			else { 
//				say("not moving"); 
			}
			
			LocalSpace newSpace = new LocalSpace(newLazers, newDefender);
			
			return newSpace;
		}
		
		/**
		 * takes a proposed movement for the Defender as well as a distance into the future, and determines if the proposed 
		 * move is going to get the Defender killed. If the proposed move does not put the defender under a laser, it returns 
		 * the same move. If the move puts the Defender in danger, it figures out the best move to do.
		 * @param offsetsToTry = proposed moves for the Defender, in the order in which to try them
		 * @param foresight = number of steps into the future to consider.
		 * @return
		 */
		public int evaluateMove(int [] offsetsToTry, int foresight) {
			
			// look at the first choice, if it's good for next step, just do it.
			LocalSpace nextSpace = advanceStep(offsetsToTry[0]);
			if(!defenderIsBelowLaser(nextSpace)){
				return offsetsToTry[0];
			}
			
			int[] lifespans = new int[offsetsToTry.length]; // should always be three: left, right, stay put in some order
			int defenderOffset = 0; //  dummy initialization to keep Eclipse happy
			boolean actionIsOK = false;
			
			/*
			 * could use a while loop here; have chosen a for instead because if none of the propose offsets work out, we don't want
			 * to loop indefinitely. Just pick the one that works when it is discovered; if none work, evaluate which is best after, then use it.
			 */
			for(int i = 0; i < offsetsToTry.length; i++){
				defenderOffset = offsetsToTry[i];
				nextSpace = advanceStep(defenderOffset);
				
//				say("evaluating offset " + defenderOffset);
				lifespans[i] = surviveTime(defenderOffset, foresight);
				
				// check if the move does not put us under a laser, or at least we can survive if it does
				if(!defenderIsBelowLaser(nextSpace) || lifespans[i] > foresight){ 
					actionIsOK = true;
					break; // the chosen offset is either clear of lasers or will be if we repeat it multiple times 
				}
				// if not, great! done.
				// else see if we can last 'foresight' steps with offset
				// if so, great! done.
				// else loop, try the next offset
				// if none work, pick the one that will maximize lifespan
			}
			
			if(!actionIsOK){
				// chose the best action by comparing the lifespans for the possible moves.
				if(lifespans[0] >= lifespans[1] && lifespans[0] >= lifespans[2]){
					// offsetsToTry[0] is the one we want
					defenderOffset = offsetsToTry[0];
				}
				if(lifespans[1] >= lifespans[0] && lifespans[1] >= lifespans[2]){
					// offsetsToTry[1] is the one we want
					defenderOffset = offsetsToTry[1];
				}
				if(lifespans[2] >= lifespans[0] && lifespans[2] >= lifespans[1]){
					// offsetsToTry[2] is the one we want
					defenderOffset = offsetsToTry[2];
				}
			}
			
			return defenderOffset;
		}
		
		/**
		 * Determine whether the defender will survive for 'foresight' steps if it consistently moves by defenderOffset
		 * @return the number of steps the Defender will survive by taking move defenderOffset, up to 'foresight'
		 */
		private int surviveTime(int defenderOffset, int foresight){
			
			int lifespan = 0;
			// figure out what the next time-step will look like
			LocalSpace nextSpace = advanceStep(defenderOffset);
			
			// evaluate each LocalSpace for the next foresight steps, moving in defenderOffset's direction
			for(; lifespan < foresight; lifespan++){
				
				if(defenderIsHit(nextSpace)){
					// then this is not a tenable course of action
					break;
				}
				nextSpace = nextSpace.advanceStep(defenderOffset);
			}
			
//			say("survivetime: " + lifespan);
			return lifespan;
		}

		private boolean defenderIsHit(LocalSpace nextSpace) {
			for(Projectile p: nextSpace.lazers){
				if(p.getRect().intersects(nextSpace.defender)){
					return true;
				}
			}
			return false;
		}

		/**
		 * returns true if any projectile in the LocalSpace is on a collision course with the Defender
		 * @param nextSpace
		 * @return
		 */
		private boolean defenderIsBelowLaser(LocalSpace nextSpace) {
			
			for(Projectile p: nextSpace.lazers){
				// make a Rectangle with the width and x_position of laz, but extending vertically through the whole gamespace
				Rectangle pRect = new Rectangle(p.getRect());
				pRect.y = 0;
				pRect.height = InvaderSaver.getGameHeight();
				
				if(pRect.intersects(nextSpace.defender)){
					return true;
				}
			}
			return false;
		}
		
		public Boolean testHit(){
			return defenderIsHit(this);
		}
		
		public Boolean testBelow(){
			return defenderIsBelowLaser(this);
		}
		
		public int testSurviveTime(int defenderOffset, int foresight){
			return surviveTime(defenderOffset, foresight);
		}
	} // end of Defender.LocalSpace

	/* *******************************************************************************
	 * OTHER CONVENIENT METHODS
	 * *******************************************************************************/

	public void die(){
//		say("HIT!");
		dead = true;
		sprite = SpriteLib.DEFENDER_DEATH;
		image_index = 0;
	}
	
	public Boolean isDead(){
		return dead;
	}
	
	/**
	 * returns true after the death animation has played for an adequate time.
	 * @return
	 */
	public Boolean resetMe(){
		// allows the death animation to loop multiple times before resetting position
		// this sort of abusive indexing is okay because my Sprite class is the bee's knees
		if(dead && image_index >= sprite.getTotalFrames()*5){
			return true;
		}
		return false;
	}
	
	public void reset(){
		sprite = SpriteLib.DEFENDER; 
		x_position = startx;
		y_position = starty;
		dead = false;
		target = null;
	}
	
	private void FIRE(){
		if(liveAmmo < MAXAMMO && fireWait == 0){
			say("FIRING!");
			ProjectileControl.addProjectile(x_position, y_position, SpriteLib.DEFENDER_LASER, PROJECTILE_SPEED);
			liveAmmo += 1;
			fireWait = FIRE_COOLDOWN;
		}
	}
	
	/**
	 * Called by the Defender's Projectile when it is spent
	 */
	static public void reload(){
		liveAmmo -= 1;
	}
	
	/**
	 * Overloads GameObject.getRect() to allow for a shallower collision box.
	 * This way it doesn't look like the Defender gets hit before the laser reaches him, due to the
	 * prominent forward gun extending the sprite upwards.
	 * This  does also mean that the prominent forward gun is invulnerable, though the ship immediately behind it is not.
	 */
	public Rectangle getRect(){
		return new Rectangle(x_position-width/2, y_position+6-height/2, width, height-6);
	}
	
	/**
	 * Take user input and determine Defender actions
	 */
	private void executeActions(){
		//TODO: make left/right movement acceleration-based to allow for finer control?
		if(InvaderSaver.UP){
			FIRE();
		}
		if(InvaderSaver.LEFT && x_position-MOVE_SPEED > sprite.image_width/2){
			x_position -= MOVE_SPEED;
		}
		if(InvaderSaver.RIGHT && x_position+MOVE_SPEED < InvaderSaver.getGameWidth()-sprite.image_width/2){
			x_position += MOVE_SPEED;
		}
	}
	
	private static void say(String string) {
		System.out.println(string);
	}
}
